/*
 * @(#)Condition.java
 *
 * Copyright 2005 Sun Microsystems, Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   1. Redistribution of source code must retain the above copyright notice,
 *      this list of conditions and the following disclaimer.
 * 
 *   2. Redistribution in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *
 * Neither the name of Sun Microsystems, Inc. or the names of contributors may
 * be used to endorse or promote products derived from this software without
 * specific prior written permission.
 * 
 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
 * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
 * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
 * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
 * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
 * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
 * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
 * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
 * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 *
 * You acknowledge that this software is not designed or intended for use in
 * the design, construction, operation or maintenance of any nuclear facility.
 */

package org.wso2.balana.cond;

import org.wso2.balana.*;
import org.wso2.balana.attr.*;
import org.wso2.balana.attr.xacml3.AttributeDesignator;

import java.io.OutputStream;
import java.io.PrintStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.wso2.balana.ctx.EvaluationCtx;
import org.wso2.balana.xacml3.AllOfSelection;
import org.wso2.balana.xacml3.AnyOfSelection;

/**
 * Represents the XACML ConditionType type. It contains exactly one child
 * expression that is boolean and returns a single value. This class was added
 * in XACML 2.0
 * 
 * @since 2.0
 * @author Seth Proctor
 */
public class Condition implements Evaluatable {

	// a local Boolean URI that is used as the return type
	private static URI booleanIdentifier;

	// regardless of version, this contains the Condition's children
	private List<Expression> children;

	// regardless of version, this is an expression that can be evaluated
	// directly
	private Expression expression;

	// the condition function, which is only used if this is a 1.x condition
	private Function function;

	// flags whether this is XACML 1.x or 2.0
	private boolean isVersionOne;

	// initialize the boolean identifier
	static {
		try {
			booleanIdentifier = new URI(BooleanAttribute.identifier);
		} catch (Exception e) {
			// we ignore this, since it cannot happen, but it should be
			// flagged in case something changes to trip this case
			booleanIdentifier = null;
		}
	}

	/**
	 * Constructs a <code>Condition</code> as used in XACML 1.x.
	 * 
	 * @param function
	 *            the <code>Function</code> to use in evaluating the elements in
	 *            the Condition
	 * @param expressions
	 *            the contents of the Condition which will be the parameters to
	 *            the function, each of which is an <code>Expression</code>
	 * 
	 * @throws IllegalArgumentException
	 *             if the input expressions don't match the signature of the
	 *             function or if the function is invalid for use in a Condition
	 */
	public Condition(Function function, List expressions)
			throws IllegalArgumentException {
		isVersionOne = true;

		// check that the function is valid for a Condition
		checkExpression(function);

		// turn the parameters into an Apply for simplicity
		expression = new Apply(function, expressions);

		// keep track of the function and the children
		this.function = function;
		children = ((Apply) expression).getChildren();
	}

	/**
	 * Constructs a <code>Condition</code> as used in XACML 2.0.
	 * 
	 * @param expression
	 *            the child <code>Expression</code>
	 * 
	 * @throws IllegalArgumentException
	 *             if the expression is not boolean or returns a bag
	 */
	public Condition(Expression expression) throws IllegalArgumentException {
		isVersionOne = false;

		// check that the function is valid for a Condition
		checkExpression(expression);

		// store the expression
		this.expression = expression;
		// there is no function in a 2.0 Condition
		function = null;

		// store the expression as the child
		List list = new ArrayList();
		list.add(this.expression);

		children = Collections.unmodifiableList(list);
	}

	/**
	 * Private helper for the constructors that checks if a given expression is
	 * valid for the root of a Condition
	 */
	private void checkExpression(Expression xpr) {
		// make sure it's a boolean expression...
		if (!xpr.getType().equals(booleanIdentifier))
			throw new IllegalArgumentException("A Condition must return a "
					+ "boolean...cannot create " + "with " + xpr.getType());

		// ...and that it never returns a bag
		if (xpr.returnsBag())
			throw new IllegalArgumentException("A Condition must not return "
					+ "a Bag");
	}

	/**
	 * Returns an instance of <code>Condition</code> based on the given DOM
	 * root.
	 * 
	 * @param root
	 *            the DOM root of a ConditionType XML type
	 * @param metaData
	 *            the meta-data associated with the containing policy
	 * @param manager
	 *            <code>VariableManager</code> used to connect references and
	 *            definitions while parsing
	 * 
	 * @throws ParsingException
	 *             if this is not a valid ConditionType
	 */
	public static Condition getInstance(Node root, PolicyMetaData metaData,
			VariableManager manager) throws ParsingException {
		if (metaData.getXACMLVersion() < XACMLConstants.XACML_VERSION_2_0) {
			Apply cond = Apply.getConditionInstance(root,
					metaData.getXPathIdentifier(), manager);
			return new Condition(cond.getFunction(), cond.getChildren());
		} else {
			Expression xpr = null;
			NodeList nodes = root.getChildNodes();

			for (int i = 0; i < nodes.getLength(); i++) {
				if (nodes.item(i).getNodeType() == Node.ELEMENT_NODE) {
					xpr = ExpressionHandler.parseExpression(nodes.item(i),
							metaData, manager);
					break;
				}
			}

			return new Condition(xpr);
		}
	}

	/**
	 * Returns the <code>Function</code> used by this <code>Condition</code> if
	 * this is a 1.x condition, or null if this is a 2.0 condition.
	 * 
	 * @return a <code>Function</code> or null
	 */
	public Function getFunction() {
		return function;
	}

	/**
	 * Returns the <code>List</code> of children for this <code>Condition</code>
	 * . The <code>List</code> contains <code>Expression</code>s. The list is
	 * unmodifiable.
	 * 
	 * @return a <code>List</code> of <code>Expression</code>s
	 */
	public List getChildren() {
		return children;
	}

	/**
	 * Returns the type of attribute that this object will return on a call to
	 * <code>evaluate</code> . This is always a boolean, since that's all that a
	 * Condition is allowed to return.
	 * 
	 * @return the boolean type
	 */
	public URI getType() {
		return booleanIdentifier;
	}

	/**
	 * Returns whether or not this <code>Condition</code> will return a bag of
	 * values on evaluation. This always returns false, since a Condition isn't
	 * allowed to return a bag.
	 * 
	 * @return false
	 */
	public boolean returnsBag() {
		return false;
	}

	/**
	 * Returns whether or not this <code>Condition</code> will return a bag of
	 * values on evaluation. This always returns false, since a Condition isn't
	 * allowed to return a bag.
	 * 
	 * @deprecated As of 2.0, you should use the <code>returnsBag</code> method
	 *             from the super-interface <code>Expression</code>.
	 * 
	 * @return false
	 */
	public boolean evaluatesToBag() {
		return false;
	}

	/**
	 * Evaluates the <code>Condition</code> by evaluating its child
	 * <code>Expression</code>.
	 * 
	 * @param context
	 *            the representation of the request
	 * 
	 * @return the result of trying to evaluate this condition object
	 */
	public EvaluationResult evaluate(EvaluationCtx context) {
		// Note that it's technically possible for this expression to
		// be something like a Function, which isn't Evaluatable. It
		// wouldn't make sense to have this, but it is possible. Because
		// it makes no sense, however, it's unlcear exactly what the
		// error should be, so raising the ClassCastException here seems
		// as good an approach as any for now...
		return ((Evaluatable) expression).evaluate(context);
	}

	/**
	 * Encodes this <code>Condition</code> into its XML form
	 * 
	 * @return <code>String</code>
	 */
	public String encode() {
		StringBuilder builder = new StringBuilder();
		encode(builder);
		return builder.toString();
	}

	/**
	 * Encodes this <code>Condition</code> into its XML form and writes this out
	 * to the provided <code>StringBuilder<code>
	 * 
	 * @param builder
	 *            string stream into which the XML-encoded data is written
	 */
	public void encode(StringBuilder builder) {

		if (isVersionOne) {
			builder.append("<Condition FunctionId=\"")
					.append(function.getIdentifier()).append("\">\n");

			for (Expression aChildren : children) {
				aChildren.encode(builder);
			}
		} else {
			builder.append("<Condition>\n");
			expression.encode(builder);
		}

		builder.append("</Condition>\n");
	}

	public Expression getExpression() {
		return this.expression;
	}

}
